using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using EnvDTE;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using EnvDTE80;

namespace TeamReview.Replay
{
    public abstract class ReplayLink
    {
        private readonly DTE2 shellDTE;

        protected ReplayLink(DTE2 applicationObject)
        {
            shellDTE = applicationObject;
        }

        public abstract ReplayReceipt TryShowReplay(WorkItem workItem, DTE2 applicationObject);

        protected string AbsoluteCodeFilePath(WorkItem workItem)
        {
            EnvDTE.Project project = GetProject(workItem);

            if (project != null && !String.IsNullOrEmpty(project.FullName))
            {
                FileInfo projectFileInfo = new FileInfo(project.FullName);
                return Path.Combine(projectFileInfo.DirectoryName, workItem.Fields[Constants.CODE_FILE_FIELD].Value.ToString().Trim(Path.DirectorySeparatorChar));
            }
            // Solution items
            string solutionPath = shellDTE.DTE.Solution.FullName;
            return Path.Combine(Path.GetDirectoryName(solutionPath), workItem.Fields[Constants.CODE_FILE_FIELD].Value.ToString().Trim(Path.DirectorySeparatorChar));
        }

        protected static CodeElement FindElement(EnvDTE.ProjectItems projectItems, string codeMemberName)
        {
            if (projectItems == null)
                return null;

            foreach (ProjectItem item in projectItems)
            {
                if (item.FileCodeModel != null && item.FileCodeModel.CodeElements != null)
                {
                    CodeElement childElement = FindElement(item.FileCodeModel.CodeElements, codeMemberName);
                    if (childElement != null)
                        return childElement;
                }

                if (item.ProjectItems != null)
                {
                    //if this item is a folder and has it's own project items...
                    CodeElement childElement = FindElement(item.ProjectItems, codeMemberName);
                    if (childElement != null)
                        return childElement;
                }
            }
            return null;
        }


        private static CodeElement FindElement(CodeElements elements, string codeMemberName)
        {
            foreach (CodeElement element in elements)
            {
                if (element != null && (element.Kind == vsCMElement.vsCMElementFunction ||
                    element.Kind == vsCMElement.vsCMElementProperty ||
                    element.Kind == vsCMElement.vsCMElementVariable ||
                    element.Kind == vsCMElement.vsCMElementEvent ||
                    element.Kind == vsCMElement.vsCMElementClass ||
                    element.Kind == vsCMElement.vsCMElementStruct ||
                    element.Kind == vsCMElement.vsCMElementDelegate ||
                    element.Kind == vsCMElement.vsCMElementInterface ||
                    element.Kind == vsCMElement.vsCMElementEnum ||
                    element.Kind == vsCMElement.vsCMElementNamespace)
                    )
                {
                    if (element.FullName.Equals(codeMemberName, StringComparison.InvariantCulture))
                        return element;

                    CodeElement childElement = FindElement(element.Children, codeMemberName);
                    if (childElement != null)
                        return childElement;
                }
            }
            return null;
        }

        protected EnvDTE.Project GetProject(WorkItem workItem)
        {
          if (workItem.Fields[Constants.CODE_PROJECT_FIELD] == null || workItem.Fields[Constants.CODE_PROJECT_FIELD].Value == null)
            return null;

          string projectToFind = workItem.Fields[Constants.CODE_PROJECT_FIELD].Value.ToString();

          //do it the quick way.. added after realizing there was a unique name
          try
          {
            EnvDTE.Project uniqueProject = shellDTE.Solution.Item(projectToFind);
            if (uniqueProject != null)
              return uniqueProject;
          }
          catch { }

          //do the slower old way... if unique names weren't used
          Projects projects = shellDTE.Solution.Projects;

          if (projects == null)
            return null;

          foreach (EnvDTE.Project project in projects)
          {
            EnvDTE.Project result = GetProjectFromSolutionFolderOrProject(projectToFind, project);

            //if the matching project was found - return the result, otherwise continue the loop
            if (result != null) return result;
          }

          //no match was found - return null
          return null;
        }

        /// <summary>
        /// Gets the project if it matches the passed in project or is contained in the specified solution folder (recursively).
        /// </summary>
        /// <param name="projectToFind">The uniquename of project to find.</param>
        /// <param name="project">The project to search.</param>
        /// <returns></returns>
        private EnvDTE.Project GetProjectFromSolutionFolderOrProject(string projectToFind, EnvDTE.Project project)
        {
          try
          {
            //If the provided project matches the desired project's unique name - return it
            if (project != null && !string.IsNullOrEmpty(project.UniqueName) &&
                project.UniqueName == projectToFind)
            {
              return project;
            }

            if (project != null && project.Kind == Constants.VS_PROJECT_KIND_SOLUTION_FOLDER)
            {
              //loop through each of the items in the solution folder
              foreach (EnvDTE.ProjectItem item in project.ProjectItems)
              {
                //Look at the project item (recursively) and return the matching project, if found
                EnvDTE.Project sourceItem = GetProjectFromProjectItem(projectToFind, item);

                if (sourceItem != null)
                {
                  return sourceItem;
                }

                //otherwise it was not found in this project item / solution tree - continue
              }
            }

          }
          catch { }

          return null;
        }

        /// <summary>
        /// Gets the project matching the provided name by looking at a project item and its descendants recursively.
        /// </summary>
        /// <param name="projectToFind">The unique name of the project to find.</param>
        /// <param name="projectItem">The project item to search.</param>
        /// <returns></returns>
        private EnvDTE.Project GetProjectFromProjectItem(string projectToFind, EnvDTE.ProjectItem projectItem)
        {
          //get subfolders again - if the project item represents a solution folder
          if (projectItem.Kind == Constants.VS_PROJECT_KIND_SOLUTION_FOLDER ||
              projectItem.Kind == Constants.VS_PROJECT_ITEM_KIND_SOLUTION_FOLDER)
          {
            if (projectItem.ProjectItems == null && projectItem.SubProject != null)
            {
              //If the project item represents a solution folder - its SubProject object will represent the solution folder
              //that can then be used to recursively look for the matching project
              EnvDTE.Project result = GetProjectFromSolutionFolderOrProject(projectToFind, projectItem.SubProject);

              //if the matching project was found - return it
              if (result != null) return result;
            }
            else
            {
              //if the projectItem itself has subItems - loop through them to identify match (recursively)
              foreach (EnvDTE.ProjectItem item in projectItem.ProjectItems)
              {
                EnvDTE.Project foundProject = GetProjectFromProjectItem(projectToFind, item);

                //The matching project - return it
                if (foundProject != null)
                  return foundProject;
              }
            }
          }

          //No matching project was found in the provided ProjectItem's descendants
          return null;
        }


        protected static void HighlightCodeSnippet(ReplayReceipt receipt)
        {
            int startLine = receipt.Selection.TopPoint.Line + receipt.LineOffset;
            int endLine = startLine + (receipt.LineCount - 1);
            if (endLine < startLine)
                endLine = startLine;
            receipt.Selection.GotoLine(startLine, false);
            EnvDTE.EditPoint startPoint = receipt.Selection.Parent.CreateEditPoint(receipt.Selection.ActivePoint);
            receipt.Selection.GotoLine(endLine, true);
            EnvDTE.EditPoint endPoint = receipt.Selection.Parent.CreateEditPoint(receipt.Selection.ActivePoint);

            receipt.Selection.MoveToPoint(startPoint, false);
            receipt.Selection.MoveToPoint(endPoint, true);
        }


        #region The Mess in TeamReview that needs cleaned up

        protected static void FindCodeSnippet(ReplayReceipt receipt, string snippet, string identificationMethod)
        {
            if (receipt.Selection == null || String.IsNullOrEmpty(snippet))
                return;

            string[] selectionLines = receipt.Selection.Text.Split('\n');
            string[] snippetLines = snippet.Split('\n');
            int selectionStart = 0;
            int foundLines = 0;
            bool allFound = false;
            bool lineFound = false;
            bool abort = false;

            for (int i = 0; i < snippetLines.Length && !allFound && !abort; i++)
            {
                string snippetLine = Regex.Replace(snippetLines[i], "\\s+", " ").Trim();
                lineFound = false;
                if (String.IsNullOrEmpty(snippetLine))
                {
                    string selectionLine = Regex.Replace(selectionLines[selectionStart], "\\s+", " ").Trim();
                    if (foundLines > 0 && String.IsNullOrEmpty(selectionLine))
                    {
                        receipt.LineCount++;
                        foundLines++;
                        selectionStart++;
                    }
                }
                else
                {
                    for (int j = selectionStart; j < selectionLines.Length && !lineFound; j++)
                    {
                        string selectionLine = Regex.Replace(selectionLines[j], "\\s+", " ").Trim();

                        if (foundLines == 0)
                        {
                            //if there is no match so far
                            if (snippetLines.Length == 1 && selectionLine.IndexOf(snippetLine) >= 0)
                            {
                                //and the snippet is only one line long then it can match any part of the selection line
                                receipt.LineOffset = j;
                                receipt.LineCount++;
                                foundLines++;
                                selectionStart = j + 1;
                                lineFound = true;
                                allFound = true;
                            }
                            else if (selectionLine.Length == (selectionLine.IndexOf(snippetLine) + snippetLine.Length))
                            {
                                //and the snippet is multi lines the snippet must match the last part of the selection line
                                receipt.LineOffset = j;
                                receipt.LineCount++;
                                foundLines++;
                                selectionStart = j + 1;
                                lineFound = true;
                            }
                        }
                        else if (i == snippetLines.Length - 1)
                        {
                            if (String.IsNullOrEmpty(selectionLine))
                            {
                                receipt.LineCount++;
                                //else if this is the last line of the snippet, then it must macth the first part of the selection line
                            }
                            else if (selectionLine.IndexOf(snippetLine) == 0)
                            {
                                receipt.LineCount++;
                                foundLines++;
                                selectionStart = j + 1;
                                allFound = true;
                                lineFound = true;
                            }
                        }
                        else if (foundLines > 0)
                        {
                            //it's the middle of the snippet, the whole line has to be empty or match
                            if (selectionLine.Length == 0)
                            {
                                receipt.LineCount++;
                                selectionStart = j + 1;
                            }
                            else if (selectionLine.Equals(snippetLine, StringComparison.InvariantCultureIgnoreCase))
                            {
                                receipt.LineCount++;
                                foundLines++;
                                selectionStart = j + 1;
                                lineFound = true;
                            }
                        }
                    }

                    if (!lineFound)
                        abort = true;
                }
            }

            if (allFound)
            {
                receipt.ItemWasFound = true;
                receipt.Outcome = identificationMethod;
            }
            else
            {
                receipt.LineCount = 0;
                receipt.LineOffset = 0;
            }
        }

        #endregion

    }
}
